package xiaochangsama.the_age_of_immortals;

import net.minecraftforge.fml.network.PacketDistributor;
import net.minecraftforge.fml.network.NetworkEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.Capability;

import net.minecraft.world.storage.WorldSavedData;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.World;
import net.minecraft.world.IWorld;
import net.minecraft.world.IServerWorld;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.Direction;
import net.minecraft.network.PacketBuffer;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.Entity;
import net.minecraft.client.Minecraft;

import java.util.function.Supplier;

public class TheAgeOfImmortalsModVariables {
	public TheAgeOfImmortalsModVariables(TheAgeOfImmortalsModElements elements) {
		elements.addNetworkMessage(WorldSavedDataSyncMessage.class, WorldSavedDataSyncMessage::buffer, WorldSavedDataSyncMessage::new,
				WorldSavedDataSyncMessage::handler);
		elements.addNetworkMessage(PlayerVariablesSyncMessage.class, PlayerVariablesSyncMessage::buffer, PlayerVariablesSyncMessage::new,
				PlayerVariablesSyncMessage::handler);
		FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init);
	}

	private void init(FMLCommonSetupEvent event) {
		CapabilityManager.INSTANCE.register(PlayerVariables.class, new PlayerVariablesStorage(), PlayerVariables::new);
	}

	@SubscribeEvent
	public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
		if (!event.getPlayer().world.isRemote()) {
			WorldSavedData mapdata = MapVariables.get(event.getPlayer().world);
			WorldSavedData worlddata = WorldVariables.get(event.getPlayer().world);
			if (mapdata != null)
				TheAgeOfImmortalsMod.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) event.getPlayer()),
						new WorldSavedDataSyncMessage(0, mapdata));
			if (worlddata != null)
				TheAgeOfImmortalsMod.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) event.getPlayer()),
						new WorldSavedDataSyncMessage(1, worlddata));
		}
	}

	@SubscribeEvent
	public void onPlayerChangedDimension(PlayerEvent.PlayerChangedDimensionEvent event) {
		if (!event.getPlayer().world.isRemote()) {
			WorldSavedData worlddata = WorldVariables.get(event.getPlayer().world);
			if (worlddata != null)
				TheAgeOfImmortalsMod.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) event.getPlayer()),
						new WorldSavedDataSyncMessage(1, worlddata));
		}
	}
	public static class WorldVariables extends WorldSavedData {
		public static final String DATA_NAME = "the_age_of_immortals_worldvars";
		public WorldVariables() {
			super(DATA_NAME);
		}

		public WorldVariables(String s) {
			super(s);
		}

		@Override
		public void read(CompoundNBT nbt) {
		}

		@Override
		public CompoundNBT write(CompoundNBT nbt) {
			return nbt;
		}

		public void syncData(IWorld world) {
			this.markDirty();
			if (world instanceof World && !world.isRemote())
				TheAgeOfImmortalsMod.PACKET_HANDLER.send(PacketDistributor.DIMENSION.with(((World) world)::getDimensionKey),
						new WorldSavedDataSyncMessage(1, this));
		}
		static WorldVariables clientSide = new WorldVariables();
		public static WorldVariables get(IWorld world) {
			if (world instanceof ServerWorld) {
				return ((ServerWorld) world).getSavedData().getOrCreate(WorldVariables::new, DATA_NAME);
			} else {
				return clientSide;
			}
		}
	}

	public static class MapVariables extends WorldSavedData {
		public static final String DATA_NAME = "the_age_of_immortals_mapvars";
		public double worldPlayerID = 1.0;
		public MapVariables() {
			super(DATA_NAME);
		}

		public MapVariables(String s) {
			super(s);
		}

		@Override
		public void read(CompoundNBT nbt) {
			worldPlayerID = nbt.getDouble("worldPlayerID");
		}

		@Override
		public CompoundNBT write(CompoundNBT nbt) {
			nbt.putDouble("worldPlayerID", worldPlayerID);
			return nbt;
		}

		public void syncData(IWorld world) {
			this.markDirty();
			if (world instanceof World && !world.isRemote())
				TheAgeOfImmortalsMod.PACKET_HANDLER.send(PacketDistributor.ALL.noArg(), new WorldSavedDataSyncMessage(0, this));
		}
		static MapVariables clientSide = new MapVariables();
		public static MapVariables get(IWorld world) {
			if (world instanceof IServerWorld) {
				return ((IServerWorld) world).getWorld().getServer().getWorld(World.OVERWORLD).getSavedData().getOrCreate(MapVariables::new,
						DATA_NAME);
			} else {
				return clientSide;
			}
		}
	}

	public static class WorldSavedDataSyncMessage {
		public int type;
		public WorldSavedData data;
		public WorldSavedDataSyncMessage(PacketBuffer buffer) {
			this.type = buffer.readInt();
			this.data = this.type == 0 ? new MapVariables() : new WorldVariables();
			this.data.read(buffer.readCompoundTag());
		}

		public WorldSavedDataSyncMessage(int type, WorldSavedData data) {
			this.type = type;
			this.data = data;
		}

		public static void buffer(WorldSavedDataSyncMessage message, PacketBuffer buffer) {
			buffer.writeInt(message.type);
			buffer.writeCompoundTag(message.data.write(new CompoundNBT()));
		}

		public static void handler(WorldSavedDataSyncMessage message, Supplier<NetworkEvent.Context> contextSupplier) {
			NetworkEvent.Context context = contextSupplier.get();
			context.enqueueWork(() -> {
				if (!context.getDirection().getReceptionSide().isServer()) {
					if (message.type == 0)
						MapVariables.clientSide = (MapVariables) message.data;
					else
						WorldVariables.clientSide = (WorldVariables) message.data;
				}
			});
			context.setPacketHandled(true);
		}
	}
	@CapabilityInject(PlayerVariables.class)
	public static Capability<PlayerVariables> PLAYER_VARIABLES_CAPABILITY = null;
	@SubscribeEvent
	public void onAttachCapabilities(AttachCapabilitiesEvent<Entity> event) {
		if (event.getObject() instanceof PlayerEntity && !(event.getObject() instanceof FakePlayer))
			event.addCapability(new ResourceLocation("the_age_of_immortals", "player_variables"), new PlayerVariablesProvider());
	}
	private static class PlayerVariablesProvider implements ICapabilitySerializable<INBT> {
		private final LazyOptional<PlayerVariables> instance = LazyOptional.of(PLAYER_VARIABLES_CAPABILITY::getDefaultInstance);
		@Override
		public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
			return cap == PLAYER_VARIABLES_CAPABILITY ? instance.cast() : LazyOptional.empty();
		}

		@Override
		public INBT serializeNBT() {
			return PLAYER_VARIABLES_CAPABILITY.getStorage().writeNBT(PLAYER_VARIABLES_CAPABILITY, this.instance.orElseThrow(RuntimeException::new),
					null);
		}

		@Override
		public void deserializeNBT(INBT nbt) {
			PLAYER_VARIABLES_CAPABILITY.getStorage().readNBT(PLAYER_VARIABLES_CAPABILITY, this.instance.orElseThrow(RuntimeException::new), null,
					nbt);
		}
	}

	private static class PlayerVariablesStorage implements Capability.IStorage<PlayerVariables> {
		@Override
		public INBT writeNBT(Capability<PlayerVariables> capability, PlayerVariables instance, Direction side) {
			CompoundNBT nbt = new CompoundNBT();
			nbt.putDouble("youYongSuDu", instance.youYongSuDu);
			nbt.putDouble("yinYang", instance.yinYang);
			nbt.putDouble("xiuWeiMax", instance.xiuWeiMax);
			nbt.putDouble("xiuWei", instance.xiuWei);
			nbt.putDouble("xiuLianSuDu", instance.xiuLianSuDu);
			nbt.putDouble("xianTianJiaDian", instance.xianTianJiaDian);
			nbt.putDouble("wuXing", instance.wuXing);
			nbt.putDouble("tu", instance.tu);
			nbt.putDouble("suDu", instance.suDu);
			nbt.putDouble("shui", instance.shui);
			nbt.putDouble("shouYuanJiDian", instance.shouYuanJiDian);
			nbt.putDouble("shouChang", instance.shouChang);
			nbt.putDouble("shanBi", instance.shanBi);
			nbt.putDouble("rouShenQiangDu", instance.rouShenQiangDu);
			nbt.putDouble("renXing", instance.renXing);
			nbt.putDouble("qiYun", instance.qiYun);
			nbt.putDouble("qiXueMax", instance.qiXueMax);
			nbt.putDouble("qingYing", instance.qingYing);
			nbt.putDouble("playerID", instance.playerID);
			nbt.putDouble("mu", instance.mu);
			nbt.putDouble("lingQiNongDu", instance.lingQiNongDu);
			nbt.putDouble("lingQiang", instance.lingQiang);
			nbt.putDouble("lingLiMax", instance.lingLiMax);
			nbt.putDouble("lingLiKongZhi", instance.lingLiKongZhi);
			nbt.putDouble("lingLi", instance.lingLi);
			nbt.putDouble("liLiang", instance.liLiang);
			nbt.putDouble("level", instance.level);
			nbt.putDouble("jiTuiKangXing", instance.jiTuiKangXing);
			nbt.putDouble("jingShenLi", instance.jingShenLi);
			nbt.putDouble("jingMaiDianShu", instance.jingMaiDianShu);
			nbt.putDouble("jin", instance.jin);
			nbt.putDouble("huo", instance.huo);
			nbt.putDouble("huiXie", instance.huiXie);
			nbt.putDouble("huiLing", instance.huiLing);
			nbt.putDouble("genJi", instance.genJi);
			nbt.putDouble("geDang", instance.geDang);
			nbt.putBoolean("firstTimeJoinIn", instance.firstTimeJoinIn);
			nbt.putDouble("fanJi", instance.fanJi);
			nbt.putDouble("daZuoHuiXie", instance.daZuoHuiXie);
			nbt.putDouble("daZuoHuiLing", instance.daZuoHuiLing);
			nbt.putDouble("daZuoGenJi", instance.daZuoGenJi);
			nbt.putDouble("chuanTou", instance.chuanTou);
			nbt.putDouble("baoJiShangHai", instance.baoJiShangHai);
			nbt.putDouble("baoJi", instance.baoJi);
			nbt.putDouble("xiuLianZhuangTai", instance.xiuLianZhuangTai);
			return nbt;
		}

		@Override
		public void readNBT(Capability<PlayerVariables> capability, PlayerVariables instance, Direction side, INBT inbt) {
			CompoundNBT nbt = (CompoundNBT) inbt;
			instance.youYongSuDu = nbt.getDouble("youYongSuDu");
			instance.yinYang = nbt.getDouble("yinYang");
			instance.xiuWeiMax = nbt.getDouble("xiuWeiMax");
			instance.xiuWei = nbt.getDouble("xiuWei");
			instance.xiuLianSuDu = nbt.getDouble("xiuLianSuDu");
			instance.xianTianJiaDian = nbt.getDouble("xianTianJiaDian");
			instance.wuXing = nbt.getDouble("wuXing");
			instance.tu = nbt.getDouble("tu");
			instance.suDu = nbt.getDouble("suDu");
			instance.shui = nbt.getDouble("shui");
			instance.shouYuanJiDian = nbt.getDouble("shouYuanJiDian");
			instance.shouChang = nbt.getDouble("shouChang");
			instance.shanBi = nbt.getDouble("shanBi");
			instance.rouShenQiangDu = nbt.getDouble("rouShenQiangDu");
			instance.renXing = nbt.getDouble("renXing");
			instance.qiYun = nbt.getDouble("qiYun");
			instance.qiXueMax = nbt.getDouble("qiXueMax");
			instance.qingYing = nbt.getDouble("qingYing");
			instance.playerID = nbt.getDouble("playerID");
			instance.mu = nbt.getDouble("mu");
			instance.lingQiNongDu = nbt.getDouble("lingQiNongDu");
			instance.lingQiang = nbt.getDouble("lingQiang");
			instance.lingLiMax = nbt.getDouble("lingLiMax");
			instance.lingLiKongZhi = nbt.getDouble("lingLiKongZhi");
			instance.lingLi = nbt.getDouble("lingLi");
			instance.liLiang = nbt.getDouble("liLiang");
			instance.level = nbt.getDouble("level");
			instance.jiTuiKangXing = nbt.getDouble("jiTuiKangXing");
			instance.jingShenLi = nbt.getDouble("jingShenLi");
			instance.jingMaiDianShu = nbt.getDouble("jingMaiDianShu");
			instance.jin = nbt.getDouble("jin");
			instance.huo = nbt.getDouble("huo");
			instance.huiXie = nbt.getDouble("huiXie");
			instance.huiLing = nbt.getDouble("huiLing");
			instance.genJi = nbt.getDouble("genJi");
			instance.geDang = nbt.getDouble("geDang");
			instance.firstTimeJoinIn = nbt.getBoolean("firstTimeJoinIn");
			instance.fanJi = nbt.getDouble("fanJi");
			instance.daZuoHuiXie = nbt.getDouble("daZuoHuiXie");
			instance.daZuoHuiLing = nbt.getDouble("daZuoHuiLing");
			instance.daZuoGenJi = nbt.getDouble("daZuoGenJi");
			instance.chuanTou = nbt.getDouble("chuanTou");
			instance.baoJiShangHai = nbt.getDouble("baoJiShangHai");
			instance.baoJi = nbt.getDouble("baoJi");
			instance.xiuLianZhuangTai = nbt.getDouble("xiuLianZhuangTai");
		}
	}

	public static class PlayerVariables {
		public double youYongSuDu = 1.0;
		public double yinYang = 0.0;
		public double xiuWeiMax = 0.0;
		public double xiuWei = 0.0;
		public double xiuLianSuDu = 1.0;
		public double xianTianJiaDian = 10.0;
		public double wuXing = 1.0;
		public double tu = -1.0;
		public double suDu = 0.1;
		public double shui = -1.0;
		public double shouYuanJiDian = 0;
		public double shouChang = 5.0;
		public double shanBi = 0;
		public double rouShenQiangDu = 0;
		public double renXing = 0;
		public double qiYun = 1.0;
		public double qiXueMax = 16.0;
		public double qingYing = 0.08;
		public double playerID = 0;
		public double mu = -1.0;
		public double lingQiNongDu = 0.0;
		public double lingQiang = 0;
		public double lingLiMax = 0.0;
		public double lingLiKongZhi = 0;
		public double lingLi = 0;
		public double liLiang = 1.0;
		public double level = 0;
		public double jiTuiKangXing = 0;
		public double jingShenLi = 0;
		public double jingMaiDianShu = 0;
		public double jin = -1.0;
		public double huo = 0;
		public double huiXie = 0;
		public double huiLing = 0;
		public double genJi = 30.0;
		public double geDang = 0.0;
		public boolean firstTimeJoinIn = true;
		public double fanJi = 0;
		public double daZuoHuiXie = 1.0;
		public double daZuoHuiLing = 1.0;
		public double daZuoGenJi = 0.01;
		public double chuanTou = 0;
		public double baoJiShangHai = 1.5;
		public double baoJi = 0.0;
		public double xiuLianZhuangTai = 0;
		public void syncPlayerVariables(Entity entity) {
			if (entity instanceof ServerPlayerEntity)
				TheAgeOfImmortalsMod.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) entity),
						new PlayerVariablesSyncMessage(this));
		}
	}
	@SubscribeEvent
	public void onPlayerLoggedInSyncPlayerVariables(PlayerEvent.PlayerLoggedInEvent event) {
		if (!event.getPlayer().world.isRemote())
			((PlayerVariables) event.getPlayer().getCapability(PLAYER_VARIABLES_CAPABILITY, null).orElse(new PlayerVariables()))
					.syncPlayerVariables(event.getPlayer());
	}

	@SubscribeEvent
	public void onPlayerRespawnedSyncPlayerVariables(PlayerEvent.PlayerRespawnEvent event) {
		if (!event.getPlayer().world.isRemote())
			((PlayerVariables) event.getPlayer().getCapability(PLAYER_VARIABLES_CAPABILITY, null).orElse(new PlayerVariables()))
					.syncPlayerVariables(event.getPlayer());
	}

	@SubscribeEvent
	public void onPlayerChangedDimensionSyncPlayerVariables(PlayerEvent.PlayerChangedDimensionEvent event) {
		if (!event.getPlayer().world.isRemote())
			((PlayerVariables) event.getPlayer().getCapability(PLAYER_VARIABLES_CAPABILITY, null).orElse(new PlayerVariables()))
					.syncPlayerVariables(event.getPlayer());
	}

	@SubscribeEvent
	public void clonePlayer(PlayerEvent.Clone event) {
		PlayerVariables original = ((PlayerVariables) event.getOriginal().getCapability(PLAYER_VARIABLES_CAPABILITY, null)
				.orElse(new PlayerVariables()));
		PlayerVariables clone = ((PlayerVariables) event.getEntity().getCapability(PLAYER_VARIABLES_CAPABILITY, null).orElse(new PlayerVariables()));
		clone.youYongSuDu = original.youYongSuDu;
		clone.yinYang = original.yinYang;
		clone.xiuWeiMax = original.xiuWeiMax;
		clone.xiuWei = original.xiuWei;
		clone.xiuLianSuDu = original.xiuLianSuDu;
		clone.wuXing = original.wuXing;
		clone.tu = original.tu;
		clone.suDu = original.suDu;
		clone.shui = original.shui;
		clone.shouYuanJiDian = original.shouYuanJiDian;
		clone.shouChang = original.shouChang;
		clone.shanBi = original.shanBi;
		clone.rouShenQiangDu = original.rouShenQiangDu;
		clone.renXing = original.renXing;
		clone.qiYun = original.qiYun;
		clone.qiXueMax = original.qiXueMax;
		clone.qingYing = original.qingYing;
		clone.playerID = original.playerID;
		clone.mu = original.mu;
		clone.lingQiNongDu = original.lingQiNongDu;
		clone.lingQiang = original.lingQiang;
		clone.lingLiMax = original.lingLiMax;
		clone.lingLiKongZhi = original.lingLiKongZhi;
		clone.lingLi = original.lingLi;
		clone.liLiang = original.liLiang;
		clone.level = original.level;
		clone.jiTuiKangXing = original.jiTuiKangXing;
		clone.jingShenLi = original.jingShenLi;
		clone.jingMaiDianShu = original.jingMaiDianShu;
		clone.jin = original.jin;
		clone.huo = original.huo;
		clone.huiXie = original.huiXie;
		clone.huiLing = original.huiLing;
		clone.genJi = original.genJi;
		clone.geDang = original.geDang;
		clone.firstTimeJoinIn = original.firstTimeJoinIn;
		clone.fanJi = original.fanJi;
		clone.daZuoHuiXie = original.daZuoHuiXie;
		clone.daZuoHuiLing = original.daZuoHuiLing;
		clone.daZuoGenJi = original.daZuoGenJi;
		clone.chuanTou = original.chuanTou;
		clone.baoJiShangHai = original.baoJiShangHai;
		clone.baoJi = original.baoJi;
		if (!event.isWasDeath()) {
			clone.xianTianJiaDian = original.xianTianJiaDian;
			clone.xiuLianZhuangTai = original.xiuLianZhuangTai;
		}
	}
	public static class PlayerVariablesSyncMessage {
		public PlayerVariables data;
		public PlayerVariablesSyncMessage(PacketBuffer buffer) {
			this.data = new PlayerVariables();
			new PlayerVariablesStorage().readNBT(null, this.data, null, buffer.readCompoundTag());
		}

		public PlayerVariablesSyncMessage(PlayerVariables data) {
			this.data = data;
		}

		public static void buffer(PlayerVariablesSyncMessage message, PacketBuffer buffer) {
			buffer.writeCompoundTag((CompoundNBT) new PlayerVariablesStorage().writeNBT(null, message.data, null));
		}

		public static void handler(PlayerVariablesSyncMessage message, Supplier<NetworkEvent.Context> contextSupplier) {
			NetworkEvent.Context context = contextSupplier.get();
			context.enqueueWork(() -> {
				if (!context.getDirection().getReceptionSide().isServer()) {
					PlayerVariables variables = ((PlayerVariables) Minecraft.getInstance().player.getCapability(PLAYER_VARIABLES_CAPABILITY, null)
							.orElse(new PlayerVariables()));
					variables.youYongSuDu = message.data.youYongSuDu;
					variables.yinYang = message.data.yinYang;
					variables.xiuWeiMax = message.data.xiuWeiMax;
					variables.xiuWei = message.data.xiuWei;
					variables.xiuLianSuDu = message.data.xiuLianSuDu;
					variables.xianTianJiaDian = message.data.xianTianJiaDian;
					variables.wuXing = message.data.wuXing;
					variables.tu = message.data.tu;
					variables.suDu = message.data.suDu;
					variables.shui = message.data.shui;
					variables.shouYuanJiDian = message.data.shouYuanJiDian;
					variables.shouChang = message.data.shouChang;
					variables.shanBi = message.data.shanBi;
					variables.rouShenQiangDu = message.data.rouShenQiangDu;
					variables.renXing = message.data.renXing;
					variables.qiYun = message.data.qiYun;
					variables.qiXueMax = message.data.qiXueMax;
					variables.qingYing = message.data.qingYing;
					variables.playerID = message.data.playerID;
					variables.mu = message.data.mu;
					variables.lingQiNongDu = message.data.lingQiNongDu;
					variables.lingQiang = message.data.lingQiang;
					variables.lingLiMax = message.data.lingLiMax;
					variables.lingLiKongZhi = message.data.lingLiKongZhi;
					variables.lingLi = message.data.lingLi;
					variables.liLiang = message.data.liLiang;
					variables.level = message.data.level;
					variables.jiTuiKangXing = message.data.jiTuiKangXing;
					variables.jingShenLi = message.data.jingShenLi;
					variables.jingMaiDianShu = message.data.jingMaiDianShu;
					variables.jin = message.data.jin;
					variables.huo = message.data.huo;
					variables.huiXie = message.data.huiXie;
					variables.huiLing = message.data.huiLing;
					variables.genJi = message.data.genJi;
					variables.geDang = message.data.geDang;
					variables.firstTimeJoinIn = message.data.firstTimeJoinIn;
					variables.fanJi = message.data.fanJi;
					variables.daZuoHuiXie = message.data.daZuoHuiXie;
					variables.daZuoHuiLing = message.data.daZuoHuiLing;
					variables.daZuoGenJi = message.data.daZuoGenJi;
					variables.chuanTou = message.data.chuanTou;
					variables.baoJiShangHai = message.data.baoJiShangHai;
					variables.baoJi = message.data.baoJi;
					variables.xiuLianZhuangTai = message.data.xiuLianZhuangTai;
				}
			});
			context.setPacketHandled(true);
		}
	}
}
